from typing import Annotated, Optional
import re

from fastapi import Query, Body
from pydantic import BaseModel, model_validator

from app.utils import regexes, types
from app.database.models import DEFAULT_CARD_RULE
from app.models.common import AppId, Pagination
from app.models import responses


class MyAppList(Pagination):
    """分页获取自己的应用列表"""
    name: str

    @model_validator(mode='before')
    @classmethod
    def before(cls, data):
        data = types.safe_dict(data)
        data['name'] = data.get('name', '')
        return data

    @model_validator(mode='after')
    def after(self):
        if len(self.name) > 64:
            raise responses.FailureResponseError(-102, msg='查询的应用名称长度不能超过 64 个字符')

        return self


MyAppListParams = Annotated[MyAppList, Query()]


class CreateApp(BaseModel):
    """创建一个应用"""
    name: str
    notice: str
    is_disabled: bool
    allow_recharge: bool
    max_bind: int
    time_restriction: str
    unbind_enabled: bool
    unbind_cost: int
    max_trial: int
    trial_duration: int
    ping_duration: int
    card_length: int
    card_prefix: str
    card_rule: str
    extra_data: dict

    @model_validator(mode='before')
    @classmethod
    def before(cls, data):
        data = types.safe_dict(data)
        data['name'] = data.get('name', '')
        data['notice'] = data.get('notice', '')
        data['is_disabled'] = data.get('is_disabled', False)
        data['allow_recharge'] = data.get('allow_recharge', None)
        data['max_bind'] = types.safe_int(data.get('max_bind', None), 1)
        data['time_restriction'] = data.get('time_restriction', '00:00:00-23:59:59')
        data['unbind_enabled'] = data.get('unbind_enabled', False)
        data['unbind_cost'] = types.safe_int(data.get('unbind_cost', None), 0)
        data['max_trial'] = types.safe_int(data.get('max_trial', None), 0)
        data['trial_duration'] = types.safe_int(data.get('trial_duration', None), 10)
        data['ping_duration'] = types.safe_int(data.get('ping_duration', None), 10)
        data['card_length'] = types.safe_int(data.get('card_length', None), 28)
        data['card_prefix'] = data.get('card_prefix', '')
        data['card_rule'] = data.get('card_rule', DEFAULT_CARD_RULE)
        data['extra_data'] = types.safe_loads_json(data.get('extra_data'))

        return data

    @model_validator(mode='after')
    def after(self):
        if not (2 <= len(self.name) <= 64):
            raise responses.FailureResponseError(-102, msg='应用名称长度应介于 2-64 位')

        if len(self.notice) > 512:
            raise responses.FailureResponseError(-102, msg='应用公告的长度不能超过 512 个字符')

        if not (1 <= self.max_bind <= 255):
            raise responses.FailureResponseError(-102, msg='应用卡密绑定设备的上下限为：1-255')

        if not re.fullmatch(regexes.TIMERANGE, self.time_restriction):
            raise responses.FailureResponseError(-102, msg='登录限制时段格式参考 00:00:00-23:59:59')

        if not (0 <= self.unbind_cost <= 65535):
            raise responses.FailureResponseError(-102, msg='解绑扣时时长应介于 0-65535 之间')

        if not (0 <= self.max_trial <= 255):
            raise responses.FailureResponseError(-102, msg='最大试用次数的上下限为：0-255')

        if not (10 <= self.trial_duration <= 65535):
            raise responses.FailureResponseError(-102, msg='试用时间应介于 10-65535 分钟')

        if not (10 <= self.ping_duration <= 65535):
            raise responses.FailureResponseError(-102, msg='心跳失效时间应介于 10-65535 分钟')

        if not (10 <= self.card_length <= 48):
            raise responses.FailureResponseError(-102, msg='卡密长度应介于 10-48 之间')

        if len(self.card_prefix) > 16:
            raise responses.FailureResponseError(-102, msg='卡密前缀长度不能超过 16 个字符')
        if re.fullmatch(regexes.INCLUDE_CHINESE, self.card_prefix):
            raise responses.FailureResponseError(-102, msg='卡密前缀不能包含中文字符')

        card_rule = ''.join(list(dict.fromkeys(self.card_rule)))
        if not (32 <= len(card_rule) <= 128):
            raise responses.FailureResponseError(-102, msg='卡密规则字符个数应在 32-128 之间')
        if re.fullmatch(regexes.INCLUDE_CHINESE, card_rule):
            raise responses.FailureResponseError(-102, msg='卡密规则不能包含中文字符')

        if len(str(self.extra_data)) > 1024:
            raise responses.FailureResponseError(-102, msg='额外数据长度不能超过 1024 个字符')

        return self


CreateAppJSON = Annotated[CreateApp, Body()]


class UpdateApp(AppId):
    """更新一个应用"""
    name: Optional[str]
    notice: Optional[str]
    is_disabled: Optional[bool]
    allow_recharge: Optional[bool]
    max_bind: Optional[int]
    time_restriction: Optional[str]
    unbind_enabled: Optional[bool]
    unbind_cost: Optional[int]
    max_trial: Optional[int]
    trial_duration: Optional[int]
    ping_duration: Optional[int]
    card_length: Optional[int]
    card_prefix: Optional[str]
    card_rule: Optional[str]
    extra_data: Optional[dict]

    @model_validator(mode='before')
    @classmethod
    def before(cls, data):
        data = types.safe_dict(data)
        data['name'] = data.get('name', None)
        data['notice'] = data.get('notice', None)
        data['is_disabled'] = data.get('is_disabled', None)
        data['allow_recharge'] = data.get('allow_recharge', None)
        data['max_bind'] = types.safe_int(data.get('max_bind', None), None)
        data['time_restriction'] = data.get('time_restriction', None)
        data['unbind_enabled'] = data.get('unbind_enabled', None)
        data['unbind_cost'] = types.safe_int(data.get('unbind_cost', None), None)
        data['max_trial'] = types.safe_int(data.get('max_trial', None), None)
        data['trial_duration'] = types.safe_int(data.get('trial_duration', None), None)
        data['ping_duration'] = types.safe_int(data.get('ping_duration', None), None)
        data['card_length'] = types.safe_int(data.get('card_length', None), None)
        data['card_prefix'] = data.get('card_prefix', None)
        data['card_rule'] = data.get('card_rule', None)
        data['extra_data'] = types.safe_loads_json(data.get('extra_data')) or None

        return data

    @model_validator(mode='after')
    def after(self):
        if self.notice is not None and not (2 <= len(self.name) <= 64):
            raise responses.FailureResponseError(-102, msg='应用名称长度应介于 2-64 位')

        if self.notice is not None and len(self.notice) > 512:
            raise responses.FailureResponseError(-102, msg='应用公告的长度不能超过 512 个字符')

        if self.max_bind is not None and not (1 <= self.max_bind <= 255):
            raise responses.FailureResponseError(-102, msg='应用卡密绑定设备的上下限为：1-255')

        if self.time_restriction is not None and not re.fullmatch(regexes.TIMERANGE, self.time_restriction):
            raise responses.FailureResponseError(-102, msg='登录限制时段格式参考 00:00:00-23:59:59')

        if self.unbind_cost is not None and not (0 <= self.unbind_cost <= 65535):
            raise responses.FailureResponseError(-102, msg='解绑扣时时长应介于 0-65535 之间')

        if self.max_trial is not None and not (0 <= self.max_trial <= 255):
            raise responses.FailureResponseError(-102, msg='最大试用次数的上下限为：0-255')

        if self.trial_duration is not None and not (10 <= self.trial_duration <= 65535):
            raise responses.FailureResponseError(-102, msg='试用时间应介于 10-65535 分钟')

        if self.ping_duration is not None and not (10 <= self.ping_duration <= 65535):
            raise responses.FailureResponseError(-102, msg='心跳失效时间应介于 10-65535 分钟')

        if self.card_length is not None and not (10 <= self.card_length <= 48):
            raise responses.FailureResponseError(-102, msg='卡密长度应介于 10-48 之间')

        if self.card_prefix is not None:
            if len(self.card_prefix) > 16:
                raise responses.FailureResponseError(-102, msg='卡密前缀长度不能超过 16 个字符')
            if re.fullmatch(regexes.INCLUDE_CHINESE, self.card_prefix):
                raise responses.FailureResponseError(-102, msg='卡密前缀不能包含中文字符')

        if self.card_rule is not None:
            card_rule = ''.join(list(dict.fromkeys(self.card_rule)))
            if not (32 <= len(card_rule) <= 128):
                raise responses.FailureResponseError(-102, msg='卡密规则字符个数应在 32-128 之间')
            if re.fullmatch(regexes.INCLUDE_CHINESE, card_rule):
                raise responses.FailureResponseError(-102, msg='卡密规则不能包含中文字符')

        if self.extra_data is not None and len(str(self.extra_data)) > 1024:
            raise responses.FailureResponseError(-102, msg='额外数据长度不能超过 1024 个字符')

        return self


UpdateAppJSON = Annotated[UpdateApp, Body()]


class BatchAppIds(BaseModel):
    app_ids: list[int] = []

    @model_validator(mode='before')
    @classmethod
    def before(cls, data):
        data = types.safe_dict(data)
        app_ids = []
        for raw_app_id in list(data.get('app_ids', [])):
            app_id = types.safe_int(raw_app_id, None)
            if app_id is not None:
                app_ids.append(app_id)

        data['app_ids'] = app_ids
        return data

    @model_validator(mode='after')
    def after(self):
        if type(self.app_ids) is not list:
            raise responses.FailureResponseError(-102, msg='多个应用 ID 必须是列表类型')

        if len(self.app_ids) == 0:
            raise responses.FailureResponseError(-102, msg='请传入至少 1 个应用 ID')

        return self


BatchDeleteAppsJSON = Annotated[BatchAppIds, Body()]


class GetLoginTimes(AppId):
    # 当日：0
    # 三天内：1
    # 本周：2
    # 本月：3
    # 每天 6 个时段: 4
    # 凌晨0-5/上午6-11/下午12-17/晚上18-23: 5
    type: int

    @model_validator(mode='before')
    @classmethod
    def before(cls, data):
        data = types.safe_dict(data)
        data['type'] = types.safe_int(data.get('type', 0), 0)
        return data

    @model_validator(mode='after')
    def after(self):
        if self.type not in (0, 1, 2, 3, 4, 5):
            raise responses.FailureResponseError(-102, msg='统计的类型不在范围内')

        return self


GetLoginTimesJSON = Annotated[GetLoginTimes, Body()]
GetLoginTimesParams = Annotated[GetLoginTimes, Query()]
